import typing as tp
import os
import platform
import datetime
import logging

import sanic.logging.formatter
from sanic import Sanic
from sanic_ext import Extend
from loguru import logger

from ..agent import Agent


class AppCtx(tp.TypedDict):
    agent: Agent


class App:
    def __init__(self, agent: Agent):
        self.agent = agent

        self.manger = Sanic("agent_manger")
        Extend(self.manger)
        from .api import api
        self.manger.blueprint(api)
        self.manger.register_listener(self.start_agent, "before_server_start")
        self.manger.register_listener(self.stop_agent, "after_server_stop")

    def run(self):
        from ..utils.func import is_port_open
        if is_port_open("127.0.0.1", self.agent.cfg.base.agent_port):
            raise RuntimeError(f"{self.agent.cfg.base.agent_port} 端口已占用.")

        if not os.path.exists(self.agent.cfg.base.ROOT):
            os.mkdir(self.agent.cfg.base.ROOT)
        if not os.path.exists(self.agent.cfg.log_path):
            os.mkdir(self.agent.cfg.log_path)
        if not os.path.exists(self.agent.cfg.tmp_path):
            os.mkdir(self.agent.cfg.tmp_path)
        if not os.path.exists(self.agent.cfg.task_path):
            os.mkdir(self.agent.cfg.task_path)

        if not os.path.exists(self.agent.cfg.base.allure_bin_file):
            logger.error(f"未找到allure: {self.agent.cfg.base.allure_bin_file}")
        elif platform.system() != "Windows":
            os.chmod(self.agent.cfg.base.allure_bin_file, 0o755)

        self.agent.db.create()

        # alembic会置为True
        from logging.config import dictConfig
        dictConfig({"version": 1, "disable_existing_loggers": False})

        now = datetime.datetime.now()
        logger.add(
            os.path.join(self.agent.cfg.log_path, now.strftime("%Y_%m_%d_%H_%M_%S") + f"_{os.getpid()}.log"),
            rotation="50 MB",
            level="DEBUG",
            enqueue=True
        )
        logger.add(
            os.path.join(self.agent.cfg.log_path, now.strftime("%Y_%m_%d_%H_%M_%S") + f"_{os.getpid()}_err.log"),
            rotation="50 MB",
            level="ERROR",
            enqueue=True
        )

        class CustomLog(logging.Handler):
            def emit(self, record):
                level = record.levelname
                logger.opt(
                    depth=6,
                    exception=record.exc_info
                ).log(level, self.format(record))

        for name, item in logging.Logger.manager.loggerDict.items():
            if name.startswith("sanic."):
                item.handlers.clear()
                item.handlers.append(CustomLog())

        sanic_logger = logging.getLogger("sanic")
        sanic_logger.propagate = False

        access_logger = logging.getLogger("sanic.access")
        access_logger.propagate = False
        access_handler = access_logger.handlers[0]
        access_handler.formatter = sanic.logging.formatter.ProdAccessFormatter()

        self.manger.run("0.0.0.0", single_process=True, port=self.agent.cfg.base.agent_port, access_log=True)

    async def start_agent(self, app: Sanic):
        app.ctx.agent = self.agent

        await self.agent.start()

    async def stop_agent(self, app: Sanic):
        await self.agent.stop()

    @classmethod
    def create(cls, cfg_path: str):
        agent = Agent(cfg_path)
        return cls(agent)
